package com.zws.cucumber.var;

import com.zws.cucumber.util.colorfullog.ZwsColorfulLogger;
import org.apache.commons.lang3.math.NumberUtils;
import org.slf4j.Logger;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;

import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;


/**
 * author: zws
 */
public class ZwsVar {

    public static final String VAR_RESP = "__response__";

    private static final Logger logger = ZwsColorfulLogger.of(ZwsVar.class);

    private static final ZwsVarBlock globalVarBlock = new ZwsVarBlock(null, ZwsVarScope.GLOBAL, new ConcurrentHashMap<>(64));

    protected static final ThreadLocal<ZwsVarBlock> varBlockTL = ThreadLocal.withInitial(
            () -> ZwsVarBlock.push(globalVarBlock, ZwsVarScope.SCENARIO));

    private static ZwsVarBlock varBlock() {
        return varBlockTL.get();
    }

    public static ZwsVarBlock pushProcedureBlock() {
        ZwsVarBlock block = varBlock();
        assert block.getScope() == ZwsVarScope.SCENARIO || block.getScope() == ZwsVarScope.PROCEDURE;
        varBlockTL.set(ZwsVarBlock.push(block, ZwsVarScope.PROCEDURE));
        return varBlock();
    }

    public static ZwsVarBlock popProcedureBlock() {
        ZwsVarBlock block = varBlock();
        assert block.getScope() == ZwsVarScope.PROCEDURE;
        varBlockTL.set(ZwsVarBlock.pop(block));
        return block;
    }

    public static ZwsVarBlock cloneScenarioBlock() {
        ZwsVarBlock block = varBlock();
        assert block.getScope() == ZwsVarScope.SCENARIO;
        assert block.getParent() != null;
        assert block.getParent().getScope() == ZwsVarScope.GLOBAL;
        ZwsVarBlock cloned = new ZwsVarBlock(null, ZwsVarScope.SCENARIO, new HashMap<>(block.getCopyOfShallowVariables()));
        return cloned;
    }

    public static void restoreScenarioBlock(ZwsVarBlock cloned) {
        ZwsVarBlock block = varBlock();
        assert block.getScope() == ZwsVarScope.SCENARIO;
        block.putAll(cloned.getCopyOfShallowVariables());
    }

    public static ZwsVarBlockBindings newBlockBindings(ZwsVarBlock block) {
        return new ZwsVarBlockBindings(block);
    }

    public static void put(String key, Object value) {
        logger.debug("put var, <b>[</b>{}<b>]</b>=<b>[</b>{}<b>]</b>", key, value);
        if (!NumberUtils.isNumber(key)) {
            varBlock().put(key, value);
        } else {
            throw new IllegalArgumentException("variable name should not be number");
        }
    }

    public static void putGlobal(String key, Object value) {
        logger.debug("put global var, <b>[</b>{}<b>]</b>=<b>[</b>{}<b>]</b>", key, value);
        if (!NumberUtils.isNumber(key)) {
            globalVarBlock.put(key, value);
        } else {
            throw new IllegalArgumentException("variable name should not be number");
        }
    }

    public static void putAll(Map<String, Object> variables) {
        variables.forEach(ZwsVar::put);
    }

    public static void putAllSilently(Map<String, Object> variables) {
        varBlock().putAll(variables);
    }

    @SuppressWarnings("unchecked")
    public static <T> T get(String key) {
        T value = (T) varBlock().get(key);
        logger.trace("getVar, <b>[</b>{}<b>]</b>=<b>[</b>{}<b>]</b>", key, value);
        return value;
    }

    public static <T> T getOrDefault(String key, T defaultValue) {
        T value = get(key);
        if (value == null) {
            put(key, value = defaultValue);
            logger.debug("getDefaultVar, <b>[</b>{}<b>]</b>=<b>[</b>{}<b>]</b>", key, value);
        }
        return value;
    }

    public static boolean containsKey(String key) {
        return varBlock().containsKey(key);
    }

    /**
     * not clear global variables
     */
    public static void clearAllVariables() {
        logger.debug("clear scenario and procedure variables");
        varBlockTL.remove();
    }

    public static Map<String, Object> getVariableMap() {
        return varBlock().getVariables();
    }

    public static List<String> split(String variables) {
        logger.debug("splitVariables(), variables={}", variables);
        List<String> result = Arrays.stream(variables.split(","))
            .map(String::trim)
            .collect(Collectors.toList());
        logger.debug("splitVariables(), result={}", result);
        return result;
    }

    public static String formatString(String pattern, String variableNames) {
        if (pattern == null) {
            logger.debug("formatString(), just return(pattern is null), pattern={}, variables={}", pattern, variableNames);
            return pattern;
        }
        logger.debug("formatString(), pattern={}, variables={}", pattern, variableNames);
        Object[] variables = Arrays.stream(variableNames.split(","))
            .map(ZwsVar::get)
            .toArray();
        String result = new MessageFormat(pattern).format(variables);
        logger.debug("formatString(), result={}", result);
        return result;
    }

    public static boolean needFormatString(String pattern) {
        List<String> placeholderList = Arrays.asList("{0}", "{1}", "{2}", "{3}", "{4}", "{5}", "{6}", "{7}", "{8}", "{9}");
        boolean flag = pattern != null && placeholderList.stream().anyMatch(pattern::contains);
        logger.debug("needFormatString(), result={}, pattern={}", flag, pattern);
        return flag;
    }

    // specified getter
    public static ResponseEntity<String> getResponse() {
        return ZwsVar.get(VAR_RESP);
    }

    public static String getResponseBody() {
        return getResponse().getBody();
    }

    public static HttpHeaders getResponseHeaders() {
        return getResponse().getHeaders();
    }

    public static Iterator<ZwsVarBlock> varBlockIterator() {
        return new Iterator<>() {
            ZwsVarBlock block = varBlock();
            @Override
            public boolean hasNext() { return block != null; }
            @Override
            public ZwsVarBlock next() {
                ZwsVarBlock ret = new ZwsVarBlock(null, block.getScope(), block.getCopyOfShallowVariables());
                ret.setContext(block.getContext());
                block = block.getParent();
                return ret;
            }
        };
    }

}
